home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / Peter Lewis / PNL Libraries / MyDatabase.p < prev    next >
Encoding:
Text File  |  1994-09-11  |  16.0 KB  |  577 lines  |  [TEXT/PJMM]

  1. unit MyDatabase;
  2.  
  3. interface
  4.  
  5. {$IFC undefined THINK_Pascal}
  6.     uses
  7.         Files;
  8. {$ENDC}
  9.  
  10.     const
  11.         DB_Normal = 0;
  12.         DB_CaseSensitive = $00000001;
  13.         DB_Null = 0;
  14.  
  15.     const
  16.         fileFormatErr = -10;
  17.         duplicateKeyErr = -11;
  18.         keyNotFoundErr = -12;
  19.  
  20.     function DatabaseCreate (var fs: FSSpec; hashsize: integer; flags: longInt): OSErr;
  21. { You should create the file before calling this using FSpCreate.  And existing data will be destroyed. }
  22. { hashsize is the number of hash table entries (initial file size will be around 4*hashsize }
  23. { hashsize should be prime }
  24.     function DatabaseOpen (var fs: FSSpec; var refnum: longInt): OSErr;
  25.     function DatabaseFlush (refnum: longInt): OSErr;
  26.     function DatabaseClose (refnum: longInt): OSErr;
  27.     function DatabaseAdd (refnum: longInt; key: str255; data: handle; overwriteok: boolean): OSErr;
  28.     function DatabaseSetInfo (refnum: longInt; key: str255; var id: longInt; size: longInt; overwriteok: boolean): OSErr;
  29.     function DatabaseSetChunk (refnum: longInt; id: longInt; pos, len: longInt; data: handle): OSErr;
  30.     function DatabaseGet (refnum: longInt; key: Str255; data: handle): OSErr; { data may be nil }
  31.     function DatabaseGetInfo (refnum: longInt; key: Str255; var id: longInt; var size: longInt): OSErr;
  32.     function DatabaseGetChunk (refnum: longInt; id: longInt; pos, len: longInt; data: handle): OSErr;
  33.     function DatabaseDelete (refnum: longInt; key: Str255; data: handle): OSErr; { data may be nil }
  34.     function DatabaseIndex (refnum: longInt; var pos: longInt; var key: Str255; data: handle): OSErr;
  35. { pass in zero the first time, then whatever you got last time to get next.   data may be nil }
  36.     function DatabasePack (refnum: longInt): OSErr;
  37. { uses about hashsize*8+8k memory in the heap }
  38.  
  39. implementation
  40.  
  41.     uses
  42. {$IFC undefined THINK_Pascal}
  43.         Memory, Packages, 
  44. {$ENDC}
  45.         MyFileSystemUtils, MyMemory;
  46.  
  47.     const
  48.         File_Magic = 'PLDB';
  49.         Current_Version = 1;
  50.         Max_Hash = 8001;
  51.         free_next = -1;
  52.  
  53. { File format: }
  54. { magic:longInt }
  55. { version: longint }
  56. { flags:longInt }
  57. { hashsize: integer}
  58. { hashtable: array[1..hashsize] of entryptr (offset into file) }
  59. { entry is: }
  60. { next:entryptr }
  61. { keylen:integer }
  62. { datalen:integer }
  63. { key:bytes }
  64. { data:bytes }
  65. { free entries have next=-1.  next links always point further into the file, never backwards }
  66.  
  67.     type
  68.         ShortFileHeader = record
  69.                 magic: OSType;
  70.                 version: longInt;
  71.                 flags: longInt;
  72.                 hashsize: integer;
  73.                 rn: integer; { not valid in file obviously }
  74.             end;
  75.         HashTableArray = array[0..Max_Hash] of longInt;
  76.         LongFileHeader = record
  77.                 magic: OSType;
  78.                 version: longInt;
  79.                 flags: longInt;
  80.                 hashsize: integer;
  81.                 rn: integer; { not valid in file obviously }
  82.                 hashtable: HashTableArray;
  83.             end;
  84.         FileHeaderPtr = ^LongFileHeader;
  85.         FileHeaderHandle = ^FileHeaderPtr;
  86.         HashTablePtr = ^HashTableArray;
  87.         EntryRecord = record
  88.                 next: longInt;
  89.                 keylen: integer;
  90.                 datalen: integer;
  91.             end;
  92.  
  93.     const
  94.         File_Header_Size = SizeOf(ShortFileHeader);
  95.         Entry_Size = SizeOf(EntryRecord);
  96.  
  97.     function DatabaseCreate (var fs: FSSpec; hashsize: integer; flags: longInt): OSErr;
  98.         var
  99.             err, oerr: OSErr;
  100.             fhp: FileHeaderPtr;
  101.             count: longInt;
  102.             rn: integer;
  103.             i: integer;
  104.     begin
  105.         if hashsize > Max_Hash then
  106.             hashsize := Max_Hash;
  107.         count := File_Header_Size + 4 * hashsize;
  108.         err := FSpOpenDF(fs, fsRdWrPerm, rn);
  109.         if err = noErr then begin
  110.             err := SetEOF(rn, count);
  111.             if err = noErr then
  112.                 err := MNewPtr(fhp, count);
  113.             if err = noErr then begin
  114.                 fhp^.magic := File_Magic;
  115.                 fhp^.version := Current_Version;
  116.                 fhp^.flags := flags;
  117.                 fhp^.hashsize := hashsize;
  118.                 for i := 0 to hashsize - 1 do begin
  119.                     fhp^.hashtable[i] := 0;
  120.                 end;
  121.                 err := FSWrite(rn, count, ptr(fhp));
  122.                 MDisposePtr(fhp);
  123.             end;
  124.             oerr := FSClose(rn);
  125.             if err = noErr then
  126.                 err := oerr;
  127.         end;
  128.         DatabaseCreate := err;
  129.     end;
  130.  
  131.     function DatabaseOpen (var fs: FSSpec; var refnum: longInt): OSErr;
  132.         var
  133.             err, junk: OSErr;
  134.             fh: ShortFileHeader;
  135.             rn: integer;
  136.             count: longInt;
  137.             fhp: FileHeaderPtr;
  138.     begin
  139.         err := FSpOpenDF(fs, fsRdWrPerm, rn);
  140.         if err = noErr then begin
  141.             count := File_Header_Size;
  142.             err := FSRead(rn, count, @fh);
  143.             if err = noErr then begin
  144.                 if (fh.magic <> File_magic) or (fh.version <> Current_Version) or (fh.hashsize < 1) or (fh.hashsize > Max_Hash) then begin
  145.                     err := fileFormatErr;
  146.                 end;
  147.             end;
  148.             if err = noErr then begin
  149.                 count := 4 * fh.hashsize;
  150.                 err := MNewPtr(fhp, File_header_Size + count);
  151.             end;
  152.             if err = noErr then begin
  153.                 BlockMove(@fh, ptr(fhp), File_Header_Size);
  154.                 fhp^.rn := rn;
  155.                 err := FSRead(rn, count, ptr(ord(fhp) + File_Header_Size));
  156.                 if err <> noErr then begin
  157.                     MDisposePtr(fhp);
  158.                 end;
  159.             end;
  160.             if err <> noErr then begin
  161.                 junk := FSClose(rn);
  162.             end;
  163.         end;
  164.         refnum := longInt(fhp);
  165.         if err <> noErr then begin
  166.             refnum := DB_Null;
  167.         end;
  168.         DatabaseOpen := err;
  169.     end;
  170.  
  171.     function DatabaseFlush (refnum: longInt): OSErr;
  172.         var
  173.             err, oerr: OSErr;
  174.             fhp: FileHeaderptr;
  175.             pb: ParamBlockRec;
  176.     begin
  177.         fhp := FileHeaderPtr(refnum);
  178.         err := MyFSWriteAt(fhp^.rn, fsFromStart, 0, GetPtrSize(ptr(fhp)), ptr(fhp));
  179.         if err = noErr then begin
  180.             pb.ioRefNum := fhp^.rn;
  181.             err := PBFlushFileSync(@pb);
  182.         end;
  183.         DatabaseFlush := err;
  184.     end;
  185.  
  186.     function DatabaseClose (refnum: longInt): OSErr;
  187.         var
  188.             err, oerr: OSErr;
  189.             fhp: FileHeaderptr;
  190.     begin
  191.         fhp := FileHeaderPtr(refnum);
  192.         err := MyFSWriteAt(fhp^.rn, fsFromStart, 0, GetPtrSize(ptr(fhp)), ptr(fhp));
  193.         oerr := FSClose(fhp^.rn);
  194.         if err = noErr then
  195.             err := oerr;
  196.         MDisposePtr(fhp);
  197.         DatabaseClose := err;
  198.     end;
  199.  
  200.     function Hash (var key: str255; hashsize: integer): integer;
  201.         var
  202.             h, i: integer;
  203.     begin
  204.         h := 0;
  205.         for i := 1 to length(key) do begin
  206.             h := ((32 * longInt(h)) + ord(key[i])) mod hashsize;
  207.         end;
  208.         Hash := h;
  209.     end;
  210.  
  211.     function ReadEntry (fhp: FileHeaderPtr; pos: longInt; var entry: EntryRecord; var key: Str255): OSErr;
  212.         var
  213.             err: OSErr;
  214.     begin
  215.         err := MyFSReadAt(fhp^.rn, pos, Entry_Size, @entry);
  216.         if err = noErr then begin
  217. {$PUSH}
  218. {$R-}
  219.             key[0] := chr(entry.keylen);
  220. {$POP}
  221.             if entry.keylen > 0 then begin
  222.                 err := MyFSReadAt(fhp^.rn, pos + Entry_Size, entry.keylen, @key[1]);
  223.             end;
  224.         end;
  225.         ReadEntry := err;
  226.     end;
  227.  
  228.     function Find (fhp: FileHeaderPtr; var key: Str255; var h: integer; var preoffset, offset: longInt; var entry: EntryRecord): OSErr;
  229. { err = noErr ==> no error.  offset<>0 ==> found. preoffset is the fileoffset that points to offset (even if not found) }
  230.         var
  231.             err: OSErr;
  232.             thiskey: str255;
  233.     begin
  234.         h := Hash(key, fhp^.hashsize);
  235.         preoffset := File_Header_Size + 4 * h;
  236.         offset := fhp^.hashtable[h];
  237.         err := noErr;
  238.         while (offset <> 0) & (err = noErr) do begin
  239.             err := ReadEntry(fhp, offset, entry, thiskey);
  240.             if err = noErr then begin
  241.                 if BAND(fhp^.flags, DB_CaseSensitive) <> 0 then begin
  242.                     if thiskey = key then begin
  243.                         leave;
  244.                     end;
  245.                 end
  246.                 else begin
  247.                     if IUEqualString(thiskey, key) = 0 then begin
  248.                         leave;
  249.                     end;
  250.                 end;
  251.                 preoffset := offset;
  252.                 offset := entry.next;
  253.             end;
  254.         end;
  255.         Find := err;
  256.     end;
  257.  
  258.     function WriteLink (fhp: FileHeaderPtr; pos: longInt; link: longInt): OSErr;
  259.         var
  260.             h: integer;
  261.             err: OSErr;
  262.     begin
  263.         if pos >= File_Header_Size + 4 * fhp^.hashsize then begin
  264.             err := MyFSWriteAt(fhp^.rn, fsFromStart, pos, 4, @link);
  265.         end
  266.         else begin
  267.             err := noErr;
  268.             h := (pos - File_Header_size) div 4;
  269.             fhp^.hashtable[h] := link;
  270.         end;
  271.         WriteLink := err;
  272.     end;
  273.  
  274.     function WriteFreeLink (fhp: FileHeaderPtr; pos: longInt): OSErr;
  275.         var
  276.             link: longInt;
  277.     begin
  278.         link := free_next;
  279.         WriteFreeLink := MyFSWriteAt(fhp^.rn, fsFromStart, pos, 4, @link);
  280.     end;
  281.  
  282.     function FindSpace (fhp: FileHeaderptr; key: str255; size: longInt; overwriteok: boolean; var offset: longInt): OSErr;
  283.         var
  284.             err, oerr: OSErr;
  285.             h: integer;
  286.             preoffset, v: longInt;
  287.             entry: EntryRecord;
  288.             filelen: longInt;
  289.             oldsize: longInt;
  290.     begin
  291.         err := Find(fhp, key, h, preoffset, offset, entry);
  292.         if (err = noErr) & (offset <> 0) & not overwriteok then
  293.             err := duplicateKeyErr;
  294.         if (err = noErr) & (offset <> 0) then begin
  295.             if entry.datalen = size then begin
  296. { all set }
  297.             end
  298.             else if entry.datalen > size + Entry_Size then begin
  299.                 oldsize := entry.datalen;
  300.                 entry.datalen := size;
  301.                 err := MyFSWriteAt(fhp^.rn, fsFromStart, offset, Entry_Size, @entry);
  302.                 if err = noErr then begin
  303.                     entry.next := free_next;
  304.                     entry.keylen := 0;
  305.                     entry.datalen := oldsize - size - Entry_Size;
  306.                     err := MyFSWriteAt(fhp^.rn, fsFromStart, offset + Entry_Size + length(key) + size, Entry_Size, @entry);
  307.                 end;
  308.             end
  309.             else begin
  310.                 err := WriteLink(fhp, preoffset, entry.next);
  311.                 v := free_next;
  312.                 if err = noErr then
  313.                     err := WriteFreeLink(fhp, offset);
  314.                 offset := entry.next;
  315.                 while (offset <> 0) & (err = noErr) do begin
  316.                     err := MyFSReadAt(fhp^.rn, offset, 4, @entry);
  317.                     if err = noErr then begin
  318.                         preoffset := offset;
  319.                         offset := entry.next;
  320.                     end;
  321.                 end;
  322.             end;
  323.         end;
  324.         if (err = noErr) & (offset = 0) then begin { add at end of file after entry at preoffset }
  325.             err := GetEOF(fhp^.rn, filelen);
  326.             if err = noErr then begin
  327.                 err := SetEOF(fhp^.rn, filelen + Entry_Size + length(key) + size);
  328.             end;
  329.             entry.next := 0;
  330.             entry.keylen := length(key);
  331.             entry.datalen := size;
  332.             if err = noErr then
  333.                 err := MyFSWriteAt(fhp^.rn, fsFromStart, filelen, Entry_Size, @entry);
  334.             if err = noErr then
  335.                 err := MyFSWrite(fhp^.rn, length(key), @key[1]);
  336.             if err = noErr then begin
  337.                 err := WriteLink(fhp, preoffset, filelen);
  338.             end;
  339.             offset := filelen;
  340.         end;
  341.         offset := offset + Entry_Size + length(key);
  342.         FindSpace := err;
  343.     end;
  344.  
  345.     function DatabaseAdd (refnum: longInt; key: str255; data: handle; overwriteok: boolean): OSErr;
  346.         var
  347.             err: OSErr;
  348.             fhp: FileHeaderptr;
  349.             offset: longInt;
  350.             handlesize: longInt;
  351.             state: SignedByte;
  352.     begin
  353.         fhp := FileHeaderPtr(refnum);
  354.         handlesize := GetHandleSize(data);
  355.         err := FindSpace(fhp, key, handlesize, overwriteok, offset);
  356.         if err = noErr then begin
  357.             HLockState(data, state);
  358.             err := MyFSWriteAt(fhp^.rn, fsFromStart, offset, handlesize, data^);
  359.             HSetState(data, state);
  360.         end;
  361.         DatabaseAdd := err;
  362.     end;
  363.  
  364.     function DatabaseSetInfo (refnum: longInt; key: str255; var id: longInt; size: longInt; overwriteok: boolean): OSErr;
  365.         var
  366.             err: OSErr;
  367.             fhp: FileHeaderptr;
  368.             state: SignedByte;
  369.     begin
  370.         fhp := FileHeaderPtr(refnum);
  371.         err := FindSpace(fhp, key, size, overwriteok, id);
  372.         DatabaseSetInfo := err;
  373.     end;
  374.  
  375.     function DatabaseSetChunk (refnum: longInt; id: longInt; pos, len: longInt; data: handle): OSErr;
  376.         var
  377.             err: OSErr;
  378.             fhp: FileHeaderptr;
  379.             state: SignedByte;
  380.     begin
  381.         fhp := FileHeaderPtr(refnum);
  382.         HLockState(data, state);
  383.         err := MyFSWriteAt(fhp^.rn, fsFromStart, id + pos, GetHandleSize(data), data^);
  384.         HSetState(data, state);
  385.         DatabaseSetChunk := err;
  386.     end;
  387.  
  388.     function Get (fhp: FileHeaderPtr; var key: Str255; var h: integer; var preoffset, offset: longInt; var entry: EntryRecord; data: handle): OSErr;
  389.         var
  390.             err: OSErr;
  391.             state: SignedByte;
  392.     begin
  393.         err := Find(fhp, key, h, preoffset, offset, entry);
  394.         if (err = noErr) & (offset = 0) then
  395.             err := keyNotFoundErr;
  396.         if err = noErr then begin
  397.             if data <> nil then begin
  398.                 HUnlockState(data, state);
  399.                 SetHandleSize(data, entry.datalen);
  400.                 err := MemError;
  401.                 if err = noErr then begin
  402.                     HLock(data);
  403.                     err := MyFSReadAt(fhp^.rn, offset + Entry_Size + entry.keylen, entry.datalen, data^);
  404.                 end;
  405.                 HSetState(data, state);
  406.             end;
  407.         end;
  408.         Get := err;
  409.     end;
  410.  
  411.     function DatabaseGet (refnum: longInt; key: Str255; data: handle): OSErr;
  412.         var
  413.             h: integer;
  414.             preoffset, offset: longInt;
  415.             entry: EntryRecord;
  416.     begin
  417.         DatabaseGet := Get(FileHeaderPtr(refnum), key, h, preoffset, offset, entry, data);
  418.     end;
  419.  
  420.     function DatabaseGetInfo (refnum: longInt; key: Str255; var id: longInt; var size: longInt): OSErr;
  421.         var
  422.             h: integer;
  423.             preoffset, offset: longInt;
  424.             entry: EntryRecord;
  425.     begin
  426.         DatabaseGetInfo := Get(FileHeaderPtr(refnum), key, h, preoffset, offset, entry, nil);
  427.         id := offset + Entry_Size + entry.keylen;
  428.         size := entry.datalen;
  429.     end;
  430.  
  431.     function DatabaseGetChunk (refnum: longInt; id: longInt; pos, len: longInt; data: handle): OSErr;
  432.         var
  433.             err: OSErr;
  434.             state: SignedByte;
  435.     begin
  436.         HUnlockState(data, state);
  437.         SetHandleSize(data, len);
  438.         err := MemError;
  439.         if err = noErr then begin
  440.             HLock(data);
  441.             err := MyFSReadAt(FileHeaderPtr(refnum)^.rn, id + pos, len, data^);
  442.         end;
  443.         HSetState(data, state);
  444.         DatabaseGetChunk := err; { Thanks Marcel/Metrowerks! }
  445.     end;
  446.  
  447.     function DatabaseDelete (refnum: longInt; key: Str255; data: handle): OSErr; { data may be nil }
  448.         var
  449.             err: OSErr;
  450.             fhp: FileHeaderptr;
  451.             h: integer;
  452.             preoffset, offset: longInt;
  453.             entry: EntryRecord;
  454.     begin
  455.         fhp := FileHeaderPtr(refnum);
  456.         err := Get(fhp, key, h, preoffset, offset, entry, data);
  457.         if err = noErr then begin
  458.             err := WriteLink(fhp, preoffset, entry.next);
  459.             if err = noErr then
  460.                 err := WriteFreeLink(fhp, offset);
  461.         end;
  462.         DatabaseDelete := err;
  463.     end;
  464.  
  465.     function DatabaseIndex (refnum: longInt; var pos: longInt; var key: Str255; data: handle): OSErr;
  466.         var
  467.             err: OSErr;
  468.             fhp: FileHeaderptr;
  469.             start, filelen: longInt;
  470.             entry: EntryRecord;
  471.             count: longInt;
  472.     begin
  473.         fhp := FileHeaderPtr(refnum);
  474.         start := File_Header_Size + 4 * fhp^.hashsize;
  475.         if pos = 0 then
  476.             pos := start;
  477.         err := GetEOF(fhp^.rn, filelen);
  478.         entry.next := free_next;
  479.         while (err = noErr) & (entry.next = free_next) & (start <= pos) & (pos < filelen) do begin
  480.             err := ReadEntry(fhp, pos, entry, key);
  481.             pos := pos + Entry_Size + entry.keylen + entry.datalen;
  482.         end;
  483.         if (err = noErr) & (entry.next = free_next) then
  484.             err := keyNotFoundErr;
  485.         if (err = noErr) & (data <> nil) then begin
  486.             SetHandleSize(data, entry.datalen);
  487.             err := MemError;
  488.             if err = noErr then begin
  489.                 count := entry.datalen;
  490.                 err := FSRead(fhp^.rn, count, data^);
  491.             end;
  492.         end;
  493.         DatabaseIndex := err;
  494.     end;
  495.  
  496.     function DatabasePack (refnum: longInt): OSErr;
  497.         const
  498.             buffer_size = 8192;
  499.         var
  500.             err: OSErr;
  501.             fhp: FileHeaderptr;
  502.             preoffsets, offsets: HashTablePtr;
  503.             start, filelen: longInt;
  504.             srcpos, destpos: longInt;
  505.             entry: EntryRecord;
  506.             key: Str255;
  507.             len, count: longInt;
  508.             buffer: ptr;
  509.             h: integer;
  510.     begin
  511.         fhp := FileHeaderPtr(refnum);
  512.         err := MNewPtr(preoffsets, 4 * fhp^.hashsize);
  513.         offsets := nil;
  514.         if err = noErr then
  515.             err := MNewPtr(offsets, 4 * fhp^.hashsize);
  516.         buffer := nil;
  517.         if err = noErr then
  518.             err := MNewPtr(buffer, buffer_size);
  519.         start := File_Header_Size + 4 * fhp^.hashsize;
  520.         if err = noErr then
  521.             err := GetEOF(fhp^.rn, filelen);
  522.         if err = noErr then begin
  523.             for h := 0 to fhp^.hashsize - 1 do begin
  524.                 preoffsets^[h] := File_header_Size + h * 4;
  525.                 offsets^[h] := fhp^.hashtable[h];
  526.             end;
  527.             srcpos := start;
  528.             destpos := start;
  529.             while (err = noErr) & (srcpos < filelen) do begin
  530.                 err := ReadEntry(fhp, srcpos, entry, key);
  531.                 if (err = noErr) then begin
  532.                     len := Entry_Size + entry.keylen + entry.datalen;
  533.                     if (entry.next = free_next) then begin { skip it }
  534.                         srcpos := srcpos + len;
  535.                     end
  536.                     else begin
  537. { ok, now we need to move this entry from srcpos to destpos, updating the link pointing to it }
  538. { Find hash }
  539.                         h := Hash(key, fhp^.hashsize);
  540.                         if (err = noErr) & (offsets^[h] <> srcpos) then
  541.                             err := fileFormatErr;
  542. { Update link }
  543.                         if err = noErr then
  544.                             err := WriteLink(fhp, preoffsets^[h], destpos);
  545.                         preoffsets^[h] := destpos;
  546.                         offsets^[h] := entry.next;
  547. { Copy entry }
  548.                         if srcpos = destpos then begin
  549.                             destpos := destpos + len;
  550.                             srcpos := srcpos + len;
  551.                         end
  552.                         else begin
  553.                             while (err = noErr) & (len > 0) do begin
  554.                                 count := len;
  555.                                 if count > buffer_size then
  556.                                     count := buffer_size;
  557.                                 err := MyFSReadAt(fhp^.rn, srcpos, count, buffer);
  558.                                 if err = noErr then
  559.                                     err := MyFSWriteAt(fhp^.rn, fsFromStart, destpos, count, buffer);
  560.                                 len := len - count;
  561.                                 srcpos := srcpos + count;
  562.                                 destpos := destpos + count;
  563.                             end;
  564.                         end;
  565.                     end;
  566.                 end;
  567.             end;
  568.             if err = noErr then
  569.                 err := SetEOF(fhp^.rn, destpos);
  570.         end;
  571.         MDisposePtr(preoffsets);
  572.         MDisposePtr(offsets);
  573.         MDisposePtr(buffer);
  574.         DatabasePack := err;
  575.     end;
  576.  
  577. end.